import com.sun.corba.se.impl.ior.OldObjectKeyTemplateBase;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;



/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2024-03-17
 * Time: 9:36
 */
public class TcpEchoServer {
    private ServerSocket serverSocket= null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    //tcp 是有连接的,和打电话一样,需要客户端拨号,服务器来接听

    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true){
            //通过accept来"接听电话"
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    // 1.通过这个方法来处理一次连接,连接建立的过程中就会涉及到多次请求交互响应
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线 \n", clientSocket.getInetAddress(), clientSocket.getPort());
        System.out.println();
        //循环读取客户端请求并响应/
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream() ) {
            Scanner scanner = new Scanner(inputStream);
            while(true){
                //通过 InputStream 读取数据
               /* byte[] buffer = new byte[1024];
                inputStream.read(buffer); 此处完全可以借助read来完成,read是把收到的数据放到 byte 数组中了
                                            后续根据请求处理响应还要把这个byte 数组转成String
                                            也可以使用更简单的办法完成,如上使用Scanner:*/
                if (!scanner.hasNext()) {
                    //读取完毕,客户端断开连接,就会产生读取完毕
                    System.out.printf("[%s:%d]  客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                // 1.读取请求并解析,这里注意隐藏的约定. next 读取的时候要读到空白符才会结束
                //   因此就要求客户端发来的请求必须带有空白符结尾,比如带有 \n(换行)/空格
                String request = scanner.next();
                // 2.根据请求计算响应
                String response = process(request);
                // 3.把响应给客户端
                /*通过这种方式可以写会,但是这种方法不方便给返回的响应中添加 \n
                outputStream.write(response.getBytes(), 0, response.getBytes().length);*/
                //也可以给 outputStream 套上一层,完成更方便的写入
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();


                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(),clientSocket.getPort(),
                                  request,response);

        }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            clientSocket.close();
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

// 在客户端 应用程序代码中调用对于的 API 和服务器尝试建立连接
// 客户端内核就会发起建立连接的流程, 由客户端内核给服务器内核发送请求 服务器的内核就会配合客户端这边的工作来完成连接的建立
// 这个连接建立的过程,就相当于一边拨号,一边响铃
// 需要等到用户接听后才能进行通信
//
// 内核建立的连接并不是决定性的.还需要用户程序,把这个连接进行"接听"/accept 操作
//                                                       是一个可能会产生阻塞的操作.如果当前没有客户端连过来,accept就会产生阻塞
//                                                       有一个客户端连过来,一次返回一个
//                                                       有若干的客户端连接过来,就会返回多个
// 接下来才能进行后续通信
// -------------------------------------------------------------------------------------------------------------
// private  serverSocket= null ServerSocket
// Socket clientSocket = serverSocket.accept()
// 这两个Socket扮演着不同的角色
//  serverSocket 客户端本体(类似于销售)
//  clientSocket 负责后续的通信交互(类似于置业顾问)
//  每个客户端都会分配 clientSocket
//  销售负责拉客,拉倒的可能会交给对应的置业顾问/

    /*
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d],客户端上线", clientSocket.getInetAddress(), clientSocket.getPort());
    }
    TCP 有连接,在他的 socket 中就会保存对端的信息
    -------------------------------------------------------------------------------------------
    try(InputStream inputStream = clientSocket.getInputStream();
        OutputStream outputStream = clientSocket.getOutputStream() )
    TCP 面向自己金额留的.这里的字节流 和文件中的字节流是一样的
    使用和文件操作一样的类和方法完成针对 tcp socket 的读写
    inputStream 从网卡读数据
    outputStream向网卡写数据
    */
//---------------------------------------------------------------------
//  printWriter 相当于 Scanner 把输出流包装一下,方便写入



//===================================================================
// 上述的代码无法正常在客户端返回一个数据,
//  可能是因为客户端请求没能成功发送出去
//  也可能是服务器响应没由成功返回回来
// 之所以出现上述情况 printWriter 内置的缓冲区在作祟
// IO 操作都是比较低效的操作
// 就希望能够让低效操作,尽量少一些
//
// 引入缓冲区
// 先把要写入网卡的数据放到内存缓冲区中,等到攒一波之后再统一发送
//                               (多次IO 合并成了一次)
// 但是也有一个问题,如果发送的数据很少,此时由于缓冲区还没满,数据就待在缓冲区里,没有被真正发送出去
// ----------------------------------------------------------------------------
// 缓冲区,对于程序的影响挺大的
// 手动刷新缓冲区
// printWriter 提供了 flush方法.刷新缓冲区的效果
//                   冲刷缓冲区
//------------------------------------------------------------------------------


